﻿// Nova.CodeDOM usage examples.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;

using Nova.CodeDOM;    // The CodeDOM namespace
using Nova.Utilities;  // Handy utility methods

namespace Nova.Examples
{
    /// <summary>
    /// This program contains examples for using the Nova.CodeDOM C# object model library.
    /// </summary>
    class Program
    {
        #region /* MAIN METHOD */

        static void Main()
        {
            // Determine the application name and if we're running in VS
            string appName = AppDomain.CurrentDomain.FriendlyName;
            if (appName.EndsWith(".exe"))
                appName = appName.Substring(0, appName.Length - 4);
            if (appName.EndsWith(".vshost"))
                appName = appName.Substring(0, appName.Length - 7);

            // Change from the output directory to the project root, so the examples can find project files
            Directory.SetCurrentDirectory(FileUtil.GetBaseDirectory());

            Version appVersion = typeof(Program).Assembly.GetName().Version;
            string appDescription = appName + " " + appVersion.Major + "." + appVersion.Minor;
            Console.WriteLine(appDescription + "  -  C# CodeDOM Examples.\nCopyright (C) 2011-2012 Inevitable Software, all rights reserved.");

            // Execute all public examples in this class
            Log.WriteLine("\nExecuting Nova.CodeDOM examples...\n");
            MethodInfo[] methodInfos = Assembly.GetExecutingAssembly().GetType("Nova.Examples.Program").GetMethods(BindingFlags.Public | BindingFlags.Static);
            foreach (MethodInfo methodInfo in methodInfos)
            {
                Log.WriteLine("===== Executing Example: " + methodInfo.Name + " =====");
                try
                {
                    methodInfo.Invoke(null, null);
                }
                catch (Exception ex)
                {
                    Log.Exception(ex, "executing example");
                }
                Log.WriteLine("===== " + methodInfo.Name + " Completed =====\n");
            }

            // Wait for a key press before exiting if running in VS
            if (Debugger.IsAttached)
            {
                Console.WriteLine("DONE.  Press any key to exit.");
                Console.ReadKey();
            }
        }

        #endregion

        #region /* LOGGING & CONFIGURATION NOTES */

        // Nova.CodeDOM includes a logging class that logs status messages to the Console Window by default.
        // It uses this for it's own logging, but you might also find it useful.
        // Use the Log class to log your own messages like this:
        //     Log.WriteLine("message");
        //     Log.DetailWriteLine("detail message");  // Only logged if LogLevel is set to Detailed
        //     Log.Exception(ex, "message");  // Log an Exception object and message

        // To intercept these log messages, you may install your own handler method of type 'void (string, string)':
        //     Log.SetLogWriteLineCallback(LoggingMethod);

        // To change the level of logging, or turn it off, set the LogLevel property, like this:
        //     Log.LogLevel = Log.Level.Detailed;
        //     Log.LogLevel = Log.Level.None;

        // NOTE: If you do not intercept or turn off logging, it might slow down processing considerably if
        //       many messages are being sent to the console.

        // Various runtime options are available in a config file, and are documented there.  See the App.config
        // file as an example and a template for your own config file. You may wish to move some or all of these options
        // to the config file of the application that uses Nova.CodeDOM.
        // All configuration options may also be controlled manually at runtime by setting the appropriate static class
        // member, such as:
        //     Log.LogLevel = Log.Level.Detailed;
        //     CodeObject.UseTabs = true;
        //     CodeObject.AutomaticFormattingCleanup = false;
        //     CodeObject.AutomaticCodeCleanup = false;

        #endregion

        #region /* LOADING EXAMPLES */

        /// <summary>
        /// Load a single CodeUnit (source file) and parse it.
        /// </summary>
        public static void LoadCodeUnit()
        {
            CodeUnit codeUnit = CodeUnit.Load("Program.cs");
            if (codeUnit != null)
                Log.WriteLine("File '" + codeUnit.Name + "' successfully loaded, parsed.");
        }

        /// <summary>
        /// Load a code fragment and parse it.
        /// </summary>
        public static void LoadCodeFragment()
        {
            // A code fragment can be a type or method declaration, or just a chunk of code.  Formatting is not that
            // important, including newlines (although newlines - or the lack of them - will be preserved unless the
            // code is specifically re-formatted).  Invalid code will produce parse errors.
            const string fragment = "int sum = 0; for (int i = 1; i < 10; ++i) sum += i;";

            CodeUnit codeUnit = CodeUnit.LoadFragment(fragment, "Fragment");
            if (codeUnit != null)
                Log.WriteLine("Code fragment '" + codeUnit.Name + "' successfully parsed.");
        }

        #endregion

        #region /* CODE GENERATION & SAVING EXAMPLES */

        /// <summary>
        /// Generate a new CodeUnit (with code in it) and save it.
        /// </summary>
        public static void GenerateNewCodeUnit()
        {
            // Create a new solution and project
            const string path = "Generated\\";

            // Create a file, and put some code in it
            CodeUnit codeUnit = new CodeUnit(path + "Program");  // file extension will default to '.cs' if omitted
            codeUnit.Add(
                new UsingDirective(codeUnit.ParseName("System")),
                new UsingDirective(codeUnit.ParseName("System.Text"))
                );

            NamespaceDecl namespaceDecl = new NamespaceDecl(codeUnit.ParseName("Generated"));
            codeUnit.Add(namespaceDecl);
            ClassDecl classDecl = new ClassDecl("Program");
            namespaceDecl.Add(classDecl);
            MethodDecl methodDecl = new MethodDecl("Main", typeof(void), Modifiers.Static, new ParameterDecl("args", typeof(string[])));
            methodDecl.Add(new Comment("Add code here"));
            classDecl.Add(methodDecl);

            // Note the use of 'codeUnit.ParseName()' in the UsingDirectives (which aren't necessary in this case, just shown as
            // examples) - this is a helper method that parses a string into an expression of NamespaceRef and/or TypeRefs and Dot
            // operators if present.  If the namespaces or types can't be found in scope, then UnresolvedRefs will be used.

            // Manually created objects will be default formatted - there's no reason to worry about braces, parentheses, commas,
            // semi-colons, newlines, whitespace, or anything else that is purely syntax-related and not semantic.  However, the
            // default formatting can be overridden using various properties, such as NewLines, HasBraces, HasParens.  You may
            // use '.IsFirstOnLine = true' to force an object to start on a new line (or '.NewLines = 1'), or '.IsSingleLine = true'
            // to force an Expression or Statement to be formatted on a single line.

            // For more examples of manual code creation, see ManualTests.GenerateFullTest() in the Nova.Test solution, which
            // generates a replica of the related FullTest.cs file, which includes most C# language features.

            // Save the CodeUnit
            codeUnit.Save();
            Log.WriteLine("Code generated and saved.");

            // You may also use '.SaveAs(string)' to save under a different file name.
        }

        /// <summary>
        /// Generate a code fragment, and convert it to a string.
        /// </summary>
        public static void GenerateCodeFragment()
        {
            // Any code object can be created without a parent, and with whatever children are desired.  For example, you might
            // create a ClassDecl or a MethodDecl, or you can create a BlockDecl (a block of statements delimited by braces, as
            // shown below), or you can create an individual Statement or Expression of any kind.
            LocalDecl sum = new LocalDecl("sum", typeof(int), 0);
            LocalDecl flag = new LocalDecl("flag", typeof(bool), false);
            flag.EOLComment = "some flag";
            LocalDecl i = new LocalDecl("i", typeof(int), 1);
            For @for = new For(i, new LessThan(i, 10), new Increment(i),
                new Block(new AddAssign(sum, i), new Assignment(flag, true)));
            @for.Comment = "calculate a sum";
            BlockDecl block = new BlockDecl(sum, flag, @for);

            // When a named code object (such as 'i' above), is used where an Expression is expected, there are implicit conversion
            // operators which create SymbolicRefs (in this case, a LocalRef), avoiding the need to call '.CreateRef()' each time.
            // Likewise, Types (such as 'typeof(int)') used for Expressions are implicitly converted to TypeRefs (avoiding the need to
            // use 'new TypeRef(typeof(int))', and literals (such as '0' or 'false') are implicitly converted to Literals (avoiding
            // the need to use 'new Literal(0)' or 'new Literal(false)'.

            // Only CodeUnit objects are mapped to files.  For all other code objects, there is no '.Save()' method.  However, you
            // may convert any code object to a string using 'AsString()' (the 'ToString()' method generates only a 'description'
            // instead of the full object, because otherwise it would cause performance issues when working in the debugger if all
            // objects rendered all of their child objects).
            Log.WriteLine(block.AsString());
        }

        #endregion

        #region /* FINDING CODE OBJECTS EXAMPLES */

        /// <summary>
        /// Finding code objects in a particular scope.
        /// </summary>
        public static void FindCodeObjects()
        {
            // Various 'Find' methods are provided on various CodeDOM types to find immediate child code objects at a particular
            // point in a code tree, examples of which are shown below.

            CodeUnit codeUnit = CodeUnit.Load("Program.cs");
            if (codeUnit != null)
            {
                // Get the first NamespaceDecl in a CodeUnit
                NamespaceDecl namespaceDecl = codeUnit.FindFirst<NamespaceDecl>();
                Log.WriteLine(namespaceDecl != null ? "Found " + namespaceDecl : "ERROR - NamespaceDecl not found");
                if (namespaceDecl != null)
                {
                    // Find a TypeDecl in a NamespaceDecl
                    TypeDecl typeDecl_Program = namespaceDecl.FindFirst<TypeDecl>("Program");
                    Log.WriteLine(typeDecl_Program != null ? "Found " + typeDecl_Program : "ERROR - TypeDecl not found");
                    if (typeDecl_Program != null)
                    {
                        // Find a MethodDecl in a TypeDecl by name
                        MethodDecl methodDecl = typeDecl_Program.FindFirst<MethodDecl>("FindCodeObjects");
                        Log.WriteLine(methodDecl != null ? "Found " + methodDecl : "ERROR - MethodDecl not found");
                        if (methodDecl != null)
                        {
                            // Find a LocalDecl in a MethodDecl by name
                            LocalDecl localDecl = methodDecl.FindFirst<LocalDecl>("codeUnit");
                            Log.WriteLine(localDecl != null ? "Found " + localDecl : "ERROR - LocalDecl not found");
                            if (localDecl != null)
                            {
                                // Find the Parent MethodDecl of a code object
                                CodeObject parentMethod = localDecl.FindParentMethod();
                                Log.WriteLine(parentMethod != null ? "Found " + parentMethod : "ERROR - parent Method not found");

                                // Find the Parent NamespaceDecl of a code object
                                NamespaceDecl parentNamespaceDecl = localDecl.FindParent<NamespaceDecl>();
                                Log.WriteLine(parentNamespaceDecl != null ? "Found " + parentNamespaceDecl : "ERROR - parent NamespaceDecl not found");
                            }

                            // Find the first 'if' statement in a MethodDecl
                            If @if = methodDecl.FindFirst<If>();
                            Log.WriteLine(@if != null ? "Found " + @if : "ERROR - If statement not found");
                            if (@if != null)
                            {
                                // Find all child 'if' statements in the body of a parent 'if' statement
                                foreach (CodeObject statement in @if.Body)
                                {
                                    if (statement is If)
                                        Log.WriteLine("Found child " + statement);
                                }
                            }
                        }
                    }
                }
            }

            // Helper methods are provided on various SymbolicRef types to find types when manually generating code
            // which conveniently return an UnresolvedRef instead of null if the item isn't found.  They can also
            // be passed an UnresolvedRef as input, so the output of one call can be passed as the input of another
            // without any checks for errors.  For examples, see ManualTests.GenerateFullTest().
            // These methods include the following signature patterns:
            //
            // Find a type in a namespace or parent type, where NorT is a Namespace or Type/TypeDecl, or an Alias to one
            //     SymbolicRef TypeRef.Find(NorT type, string name)
            //
            // Find a member of a type, where TYPE is a Type, TypeDecl, TypeRefBase, or type Alias:
            //     SymbolicRef MethodRef.Find(TYPE type, string name)
            //     SymbolicRef ConstructorRef.Find(TYPE type, params TypeRefBase[] parameterTypes)
            //     SymbolicRef PropertyRef.Find(TYPE type, string name)
            //     SymbolicRef FieldRef.Find(TYPE type, string name)
            //
            // Find a parameter of a method, where METHOD is a MethodInfo or MethodDeclBase
            //     SymbolicRef ParameterRef.Find(METHOD method, string name)
        }

        #endregion

        #region /* CODE MODIFICATION EXAMPLES */

        /// <summary>
        /// Modify code objects.
        /// </summary>
        public static void ModifyCodeObjects()
        {
            // Code objects have various properties which can be directly assigned.  Some code objects have direct
            // children code objects (such as the various parts of a For loop) which can be directly assigned to
            // LocalDecls or Expressions.  Many (such as all BlockStatements) have a Body which is a collection of
            // child code objects which can be manipulated using methods such as Add(), Remove(), and Insert().

            // Here are some examples using a small code fragment:
            const string fragment =
                "/// <see cref=\"OldClass\"/>\n"
                    + "class OldClass\n"
                    + "{\n"
                    + "    public OldClass(int arg)\n"
                    + "    { }\n"
                    + "    \n"
                    + "    private void ToBeDeletedMethod()\n"
                    + "    { }\n"
                    + "    \n"
                    + "    /// <param name=\"oldArg\">The argument.</param>\n"
                    + "    private void Method(int oldArg = -1)\n"
                    + "    {\n"
                    + "        OldClass x = new OldClass(oldArg);\n"
                    + "        for (int i = oldArg; i < 10; ++i)\n"
                    + "            x.Method(oldArg);\n"
                    + "    }\n"
                    + "}\n";
            CodeUnit codeUnit = CodeUnit.LoadFragment(fragment, "Fragment");
            if (codeUnit != null)
            {
                ClassDecl classDecl = codeUnit.FindFirst<ClassDecl>("OldClass");
                if (classDecl != null)
                {
                    // Rename 'OldClass' to 'NewClass' and change it to 'public'.
                    // Note that references to OldClass will NOT be renamed, since we're not resolving references yet.
                    classDecl.Name = "NewClass";
                    classDecl.IsPublic = true;

                    MethodDecl methodDecl = classDecl.FindFirst<MethodDecl>("Method");
                    if (methodDecl != null)
                    {
                        // Change 'Method' to 'public', and change its return type from 'void' to 'int'
                        methodDecl.IsPublic = true;
                        methodDecl.ReturnType = typeof(int);

                        // Rename the parameter of 'Method' from 'oldArg' to 'newArg', change its type from 'int' to 'long',
                        // and change its initialization from '-1' to '0'.
                        ParameterDecl parameterDecl = methodDecl.Parameters[0];
                        parameterDecl.Name = "newArg";
                        parameterDecl.Type = typeof(long);
                        parameterDecl.Initialization = 0;
                    }

                    // Insert a new method after the constructor
                    classDecl.Insert(1, new MethodDecl("InsertedMethod", typeof(void)));

                    // Remove a method
                    MethodDecl toBeDeleted = classDecl.FindFirst<MethodDecl>("ToBeDeletedMethod");
                    if (toBeDeleted != null)
                        classDecl.Remove(toBeDeleted);
                }

                // Insert an enum into the CodeUnit before the class
                EnumDecl enumDecl = new EnumDecl("Enum");
                enumDecl.Add(new EnumMemberDecl("One"), new EnumMemberDecl("Two"));
                codeUnit.Insert(0, enumDecl);

                // Display the modified code
                Log.WriteLine(codeUnit.AsString());
            }
        }

        #endregion
    }
}
